#include <iostream>
#include "Common.hpp"
#include "Socket.hpp"
#include <sys/epoll.h>

using namespace myLog;
using namespace mySocket;

class EPollServer{
public:
    const static int default_size = 64;
    const static int defaultfd = -1;

public:
    EPollServer(uint16_t port, size_t size = default_size)
        :_port(port),
        _is_running(false),
        _listensock(std::make_unique<TcpSocket>()),
        _epfd(defaultfd),
        _maxrevs(size)
    {
        // 1.创建一个TCP套接字
        _listensock->CreateServerTcpSocket(_port); 

        // 2.创建一个epoll模型
        _epfd = epoll_create(256); //后面填的参数在老内核中是底层红黑树的节点个数，但是新内核中无所谓了，只要参数大于0即可，没有上限的！
        if(_epfd < 0) {
            LOG(LogLevel::ERROR) << "epoll create error!";
            exit(EPOLL_CREATE_ERROR);
        }
        else LOG(LogLevel::INFO) << "epoll create success! epfd is : " << _epfd;

        // 3.因为后序不能直接accept！ 所以，只能把监听套接字listensockfd交给epoll模型
        int listen_sockfd = _listensock->GetFd();
        struct epoll_event ev;
        ev.events |= EPOLLIN;
        ev.data.fd = listen_sockfd;
        int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, listen_sockfd, &ev);
        if(n < 0){
            LOG(LogLevel::ERROR) << "epoll add listen_sockfd error";
            exit(EPOLL_CTL_ERROR);
        }
        else LOG(LogLevel::INFO) << "epoll add listen_sockfd success";

        //4.对存储就绪队列的的返回事件数组给定容量
        _revs = new struct epoll_event[_maxrevs];
    }
    ~EPollServer(){
        _listensock->Close();
        if(_epfd > 0) close(_epfd);
    }

    void Start(){
        _is_running = true;
        while(_is_running){
            // poll接口最后一个参数是超时时间，单位ms
            //如果设置为-1，阻塞；如果设置为0，非阻塞轮询！
            int timeout = -1 /*0*/ /*1000*/;
            int n = epoll_wait(_epfd, _revs, default_size, timeout);

            //epoll_wait也有三种返回值
            // <0 出错； =0 等待超时  >0说明有文件描述符对应的事件就绪了，返回的值是就绪文件描述符的个数！
            if(n < 0){
                LOG(LogLevel::ERROR) << "epoll wait error!";
                exit(EPOLL_WAIT_ERROR);
            }
            else if(n == 0) LOG(LogLevel::DEBUG) << "timeout...";
            else Dispatcher(n);
        }
        _is_running = false;
    }

    void Dispatcher(int revs_num){
        //此时，所有的 就绪 的事件的文件描述符都放在了数组 revs内！
        for(int i = 0; i < revs_num; ++i){
            int fd = _revs[i].data.fd;          //就绪事件对应的fd
            uint32_t revents = _revs[i].events; //就绪的事件

            // 读事件就绪
            if(revents & EPOLLIN){
                // listen sockfd 就绪
                // 能立马进行读取嘛？ 即recv / read？ 不能，因为不知道会不会被卡住！
                // 交给链接管理器
                if(fd == _listensock->GetFd()) Accepter();

                // normal sockfd 就绪
                // 交给IO处理器的读取操作
                else Recver(fd);
            }

            //后序还可以写别的事件就绪，这里也只是做echo server
            //后序会把所有的东西都带上的！
        }
    }

    // 链接管理器——链接管理 (把链接设置到内核)
    void Accepter(){
        //把新获取到的sockfd交给epoll模型
        //我们在这里至少知道，获取一次链接时不会阻塞的！ 多次不好说
        InetAddr client;
        int sockfd = _listensock->AcceptOrDie(&client);
        if(sockfd < 0){
            LOG(LogLevel::WARNING) << "get new client sockfd fail";
            return;
        }
        struct epoll_event ev;
        ev.events |= EPOLLIN;
        ev.data.fd = sockfd;
        int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);
        if(n < 0) {
            LOG(LogLevel::WARNING) << "get new link fail";
            close(sockfd);
            return;
        }
        LOG(LogLevel::INFO) << "get a new link : " << client.GetFormatStr();
    }

    // IO处理器——这里只有读
    // 这里只需要把辅助数组中，文件描述符 存储位置下标拿过来即可
    void Recver(int fd){
        char buffer[1024] = {0};
        int n = recv(fd, buffer, sizeof(buffer) - 1, 0);
        if(n < 0){
            // 读取出错了！
            LOG(LogLevel::ERROR) << "read error";
            // 需要移除epoll模型对该文件描述符的关注 注意，文件描述符要合法！
            // 需要先取消关注 然后再关fd
            int ret = epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
            if(ret > 0) LOG(LogLevel::INFO) << "epoll delete" << fd;
            close(fd);

        }
        else if(n == 0){
            // 客户端退出
            LOG(LogLevel::ERROR) << "client quit";
            int ret = epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
            if(ret > 0) LOG(LogLevel::INFO) << "epoll delete" << fd;
            close(fd);

        }
        else{
            // 正常读取
            buffer[n] = 0;
            // 这里是echo server 直接打印即可
            LOG(LogLevel::INFO) << "client says@ " << buffer;
        }
    }

    void Stop(){_is_running = false;}


private:
    uint16_t _port;
    bool _is_running;
    std::unique_ptr<Socket> _listensock;
    int _epfd; //epoll模型对应的文件描述符

    int _maxrevs;
    struct epoll_event* _revs;
};